Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/build/lib/test/policyConversion.test.ts
4772 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 assert from 'assert';
7
import { promises as fs } from 'fs';
8
import path from 'path';
9
import type { ExportedPolicyDataDto, CategoryDto } from '../policies/policyDto.ts';
10
import { BooleanPolicy } from '../policies/booleanPolicy.ts';
11
import { NumberPolicy } from '../policies/numberPolicy.ts';
12
import { ObjectPolicy } from '../policies/objectPolicy.ts';
13
import { StringEnumPolicy } from '../policies/stringEnumPolicy.ts';
14
import { StringPolicy } from '../policies/stringPolicy.ts';
15
import type { Policy, ProductJson } from '../policies/types.ts';
16
import { renderGP, renderMacOSPolicy, renderJsonPolicies } from '../policies/render.ts';
17
18
const PolicyTypes = [
19
BooleanPolicy,
20
NumberPolicy,
21
StringEnumPolicy,
22
StringPolicy,
23
ObjectPolicy
24
];
25
26
function parsePolicies(policyData: ExportedPolicyDataDto): Policy[] {
27
const categories = new Map<string, CategoryDto>();
28
for (const category of policyData.categories) {
29
categories.set(category.key, category);
30
}
31
32
const policies: Policy[] = [];
33
for (const policy of policyData.policies) {
34
const category = categories.get(policy.category);
35
if (!category) {
36
throw new Error(`Unknown category: ${policy.category}`);
37
}
38
39
let result: Policy | undefined;
40
for (const policyType of PolicyTypes) {
41
if (result = policyType.from(category, policy)) {
42
break;
43
}
44
}
45
46
if (!result) {
47
throw new Error(`Unsupported policy type: ${policy.type} for policy ${policy.name}`);
48
}
49
50
policies.push(result);
51
}
52
53
// Sort policies first by category name, then by policy name
54
policies.sort((a, b) => {
55
const categoryCompare = a.category.name.value.localeCompare(b.category.name.value);
56
if (categoryCompare !== 0) {
57
return categoryCompare;
58
}
59
return a.name.localeCompare(b.name);
60
});
61
62
return policies;
63
}
64
65
/**
66
* This is a snapshot of the data taken on Oct. 20 2025 as part of the
67
* policy refactor effort. Let's make sure that nothing has regressed.
68
*/
69
const policies: ExportedPolicyDataDto = {
70
categories: [
71
{
72
key: 'Extensions',
73
name: {
74
key: 'extensionsConfigurationTitle',
75
value: 'Extensions'
76
}
77
},
78
{
79
key: 'IntegratedTerminal',
80
name: {
81
key: 'terminalIntegratedConfigurationTitle',
82
value: 'Integrated Terminal'
83
}
84
},
85
{
86
key: 'InteractiveSession',
87
name: {
88
key: 'interactiveSessionConfigurationTitle',
89
value: 'Chat'
90
}
91
},
92
{
93
key: 'Telemetry',
94
name: {
95
key: 'telemetryConfigurationTitle',
96
value: 'Telemetry'
97
}
98
},
99
{
100
key: 'Update',
101
name: {
102
key: 'updateConfigurationTitle',
103
value: 'Update'
104
}
105
}
106
],
107
policies: [
108
{
109
key: 'chat.mcp.gallery.serviceUrl',
110
name: 'McpGalleryServiceUrl',
111
category: 'InteractiveSession',
112
minimumVersion: '1.101',
113
localization: {
114
description: {
115
key: 'mcp.gallery.serviceUrl',
116
value: 'Configure the MCP Gallery service URL to connect to'
117
}
118
},
119
type: 'string',
120
default: ''
121
},
122
{
123
key: 'extensions.gallery.serviceUrl',
124
name: 'ExtensionGalleryServiceUrl',
125
category: 'Extensions',
126
minimumVersion: '1.99',
127
localization: {
128
description: {
129
key: 'extensions.gallery.serviceUrl',
130
value: 'Configure the Marketplace service URL to connect to'
131
}
132
},
133
type: 'string',
134
default: ''
135
},
136
{
137
key: 'extensions.allowed',
138
name: 'AllowedExtensions',
139
category: 'Extensions',
140
minimumVersion: '1.96',
141
localization: {
142
description: {
143
key: 'extensions.allowed.policy',
144
value: 'Specify a list of extensions that are allowed to use. This helps maintain a secure and consistent development environment by restricting the use of unauthorized extensions. More information: https://code.visualstudio.com/docs/setup/enterprise#_configure-allowed-extensions'
145
}
146
},
147
type: 'object',
148
default: '*'
149
},
150
{
151
key: 'chat.tools.global.autoApprove',
152
name: 'ChatToolsAutoApprove',
153
category: 'InteractiveSession',
154
minimumVersion: '1.99',
155
localization: {
156
description: {
157
key: 'autoApprove2.description',
158
value: 'Global auto approve also known as "YOLO mode" disables manual approval completely for all tools in all workspaces, allowing the agent to act fully autonomously. This is extremely dangerous and is *never* recommended, even containerized environments like Codespaces and Dev Containers have user keys forwarded into the container that could be compromised.\n\nThis feature disables critical security protections and makes it much easier for an attacker to compromise the machine.'
159
}
160
},
161
type: 'boolean',
162
default: false
163
},
164
{
165
key: 'chat.mcp.access',
166
name: 'ChatMCP',
167
category: 'InteractiveSession',
168
minimumVersion: '1.99',
169
localization: {
170
description: {
171
key: 'chat.mcp.access',
172
value: 'Controls access to installed Model Context Protocol servers.'
173
},
174
enumDescriptions: [
175
{
176
key: 'chat.mcp.access.none',
177
value: 'No access to MCP servers.'
178
},
179
{
180
key: 'chat.mcp.access.registry',
181
value: 'Allows access to MCP servers installed from the registry that VS Code is connected to.'
182
},
183
{
184
key: 'chat.mcp.access.any',
185
value: 'Allow access to any installed MCP server.'
186
}
187
]
188
},
189
type: 'string',
190
default: 'all',
191
enum: [
192
'none',
193
'registry',
194
'all'
195
]
196
},
197
{
198
key: 'chat.extensionTools.enabled',
199
name: 'ChatAgentExtensionTools',
200
category: 'InteractiveSession',
201
minimumVersion: '1.99',
202
localization: {
203
description: {
204
key: 'chat.extensionToolsEnabled',
205
value: 'Enable using tools contributed by third-party extensions.'
206
}
207
},
208
type: 'boolean',
209
default: true
210
},
211
{
212
key: 'chat.agent.enabled',
213
name: 'ChatAgentMode',
214
category: 'InteractiveSession',
215
minimumVersion: '1.99',
216
localization: {
217
description: {
218
key: 'chat.agent.enabled.description',
219
value: 'Enable agent mode for chat. When this is enabled, agent mode can be activated via the dropdown in the view.'
220
}
221
},
222
type: 'boolean',
223
default: true
224
},
225
{
226
key: 'chat.promptFiles',
227
name: 'ChatPromptFiles',
228
category: 'InteractiveSession',
229
minimumVersion: '1.99',
230
localization: {
231
description: {
232
key: 'chat.promptFiles.policy',
233
value: 'Enables reusable prompt and instruction files in Chat sessions.'
234
}
235
},
236
type: 'boolean',
237
default: true
238
},
239
{
240
key: 'chat.tools.terminal.enableAutoApprove',
241
name: 'ChatToolsTerminalEnableAutoApprove',
242
category: 'IntegratedTerminal',
243
minimumVersion: '1.104',
244
localization: {
245
description: {
246
key: 'autoApproveMode.description',
247
value: 'Controls whether to allow auto approval in the run in terminal tool.'
248
}
249
},
250
type: 'boolean',
251
default: true
252
},
253
{
254
key: 'update.mode',
255
name: 'UpdateMode',
256
category: 'Update',
257
minimumVersion: '1.67',
258
localization: {
259
description: {
260
key: 'updateMode',
261
value: 'Configure whether you receive automatic updates. Requires a restart after change. The updates are fetched from a Microsoft online service.'
262
},
263
enumDescriptions: [
264
{
265
key: 'none',
266
value: 'Disable updates.'
267
},
268
{
269
key: 'manual',
270
value: 'Disable automatic background update checks. Updates will be available if you manually check for updates.'
271
},
272
{
273
key: 'start',
274
value: 'Check for updates only on startup. Disable automatic background update checks.'
275
},
276
{
277
key: 'default',
278
value: 'Enable automatic update checks. Code will check for updates automatically and periodically.'
279
}
280
]
281
},
282
type: 'string',
283
default: 'default',
284
enum: [
285
'none',
286
'manual',
287
'start',
288
'default'
289
]
290
},
291
{
292
key: 'telemetry.telemetryLevel',
293
name: 'TelemetryLevel',
294
category: 'Telemetry',
295
minimumVersion: '1.99',
296
localization: {
297
description: {
298
key: 'telemetry.telemetryLevel.policyDescription',
299
value: 'Controls the level of telemetry.'
300
},
301
enumDescriptions: [
302
{
303
key: 'telemetry.telemetryLevel.default',
304
value: 'Sends usage data, errors, and crash reports.'
305
},
306
{
307
key: 'telemetry.telemetryLevel.error',
308
value: 'Sends general error telemetry and crash reports.'
309
},
310
{
311
key: 'telemetry.telemetryLevel.crash',
312
value: 'Sends OS level crash reports.'
313
},
314
{
315
key: 'telemetry.telemetryLevel.off',
316
value: 'Disables all product telemetry.'
317
}
318
]
319
},
320
type: 'string',
321
default: 'all',
322
enum: [
323
'all',
324
'error',
325
'crash',
326
'off'
327
]
328
},
329
{
330
key: 'telemetry.feedback.enabled',
331
name: 'EnableFeedback',
332
category: 'Telemetry',
333
minimumVersion: '1.99',
334
localization: {
335
description: {
336
key: 'telemetry.feedback.enabled',
337
value: 'Enable feedback mechanisms such as the issue reporter, surveys, and other feedback options.'
338
}
339
},
340
type: 'boolean',
341
default: true
342
}
343
]
344
};
345
346
const mockProduct: ProductJson = {
347
nameLong: 'Code - OSS',
348
darwinBundleIdentifier: 'com.visualstudio.code.oss',
349
darwinProfilePayloadUUID: 'CF808BE7-53F3-46C6-A7E2-7EDB98A5E959',
350
darwinProfileUUID: '47827DD9-4734-49A0-AF80-7E19B11495CC',
351
win32RegValueName: 'CodeOSS'
352
};
353
354
const frenchTranslations = [
355
{
356
languageId: 'fr-fr',
357
languageTranslations: {
358
'': {
359
'interactiveSessionConfigurationTitle': 'Session interactive',
360
'extensionsConfigurationTitle': 'Extensions',
361
'terminalIntegratedConfigurationTitle': 'Terminal intégré',
362
'telemetryConfigurationTitle': 'Télémétrie',
363
'updateConfigurationTitle': 'Mettre à jour',
364
'chat.extensionToolsEnabled': 'Autorisez l’utilisation d’outils fournis par des extensions tierces.',
365
'chat.agent.enabled.description': 'Activez le mode Assistant pour la conversation. Lorsque cette option est activée, le mode Assistant peut être activé via la liste déroulante de la vue.',
366
'chat.mcp.access': 'Contrôle l’accès aux serveurs de protocole de contexte du modèle.',
367
'chat.mcp.access.none': 'Aucun accès aux serveurs MCP.',
368
'chat.mcp.access.registry': `Autorise l’accès aux serveurs MCP installés à partir du registre auquel VS Code est connecté.`,
369
'chat.mcp.access.any': 'Autorisez l’accès à tout serveur MCP installé.',
370
'chat.promptFiles.policy': 'Active les fichiers d’instruction et de requête réutilisables dans les sessions Conversation.',
371
'autoApprove2.description': `L’approbation automatique globale, également appelée « mode YOLO », désactive complètement l’approbation manuelle pour tous les outils dans tous les espaces de travail, permettant à l’agent d’agir de manière totalement autonome. Ceci est extrêmement dangereux et est *jamais* recommandé, même dans des environnements conteneurisés comme [Codespaces](https://github.com/features/codespaces) et [Dev Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers), où des clés utilisateur sont transférées dans le conteneur et pourraient être compromises.
372
373
Cette fonctionnalité désactive [les protections de sécurité critiques](https://code.visualstudio.com/docs/copilot/security) et facilite considérablement la compromission de la machine par un attaquant.`,
374
'mcp.gallery.serviceUrl': 'Configurer l’URL du service de la galerie MCP à laquelle se connecter',
375
'extensions.allowed.policy': 'Spécifiez une liste d’extensions autorisées. Cela permet de maintenir un environnement de développement sécurisé et cohérent en limitant l’utilisation d’extensions non autorisées. Plus d’informations : https://code.visualstudio.com/docs/setup/enterprise#_configure-allowed-extensions',
376
'extensions.gallery.serviceUrl': 'Configurer l’URL du service Place de marché à laquelle se connecter',
377
'autoApproveMode.description': 'Contrôle s’il faut autoriser l’approbation automatique lors de l’exécution dans l’outil terminal.',
378
'telemetry.feedback.enabled': 'Activez les mécanismes de commentaires tels que le système de rapport de problèmes, les sondages et autres options de commentaires.',
379
'telemetry.telemetryLevel.policyDescription': 'Contrôle le niveau de télémétrie.',
380
'telemetry.telemetryLevel.default': `Envoie les données d'utilisation, les erreurs et les rapports d'erreur.`,
381
'telemetry.telemetryLevel.error': `Envoie la télémétrie d'erreur générale et les rapports de plantage.`,
382
'telemetry.telemetryLevel.crash': `Envoie des rapports de plantage au niveau du système d'exploitation.`,
383
'telemetry.telemetryLevel.off': 'Désactive toutes les données de télémétrie du produit.',
384
'updateMode': `Choisissez si vous voulez recevoir des mises à jour automatiques. Nécessite un redémarrage après le changement. Les mises à jour sont récupérées auprès d'un service en ligne Microsoft.`,
385
'none': 'Aucun',
386
'manual': 'Désactivez la recherche de mises à jour automatique en arrière-plan. Les mises à jour sont disponibles si vous les rechercher manuellement.',
387
'start': 'Démarrer',
388
'default': 'Système'
389
}
390
}
391
}
392
];
393
394
suite('Policy E2E conversion', () => {
395
396
test('should render macOS policy profile from policies list', async () => {
397
const parsedPolicies = parsePolicies(policies);
398
const result = renderMacOSPolicy(mockProduct, parsedPolicies, []);
399
400
// Load the expected fixture file
401
const fixturePath = path.join(import.meta.dirname, 'fixtures', 'policies', 'darwin', 'com.visualstudio.code.oss.mobileconfig');
402
const expectedContent = await fs.readFile(fixturePath, 'utf-8');
403
404
// Compare the rendered profile with the fixture
405
assert.strictEqual(result.profile, expectedContent, 'macOS policy profile should match the fixture');
406
});
407
408
test('should render macOS manifest from policies list', async () => {
409
const parsedPolicies = parsePolicies(policies);
410
const result = renderMacOSPolicy(mockProduct, parsedPolicies, []);
411
412
// Load the expected fixture file
413
const fixturePath = path.join(import.meta.dirname, 'fixtures', 'policies', 'darwin', 'en-us', 'com.visualstudio.code.oss.plist');
414
const expectedContent = await fs.readFile(fixturePath, 'utf-8');
415
416
// Find the en-us manifest
417
const enUsManifest = result.manifests.find(m => m.languageId === 'en-us');
418
assert.ok(enUsManifest, 'en-us manifest should exist');
419
420
// Compare the rendered manifest with the fixture, ignoring the timestamp
421
// The pfm_last_modified field contains a timestamp that will differ each time
422
const normalizeTimestamp = (content: string) => content.replace(/<date>.*?<\/date>/, '<date>TIMESTAMP</date>');
423
assert.strictEqual(
424
normalizeTimestamp(enUsManifest.contents),
425
normalizeTimestamp(expectedContent),
426
'macOS manifest should match the fixture (ignoring timestamp)'
427
);
428
});
429
430
test('should render Windows ADMX from policies list', async () => {
431
const parsedPolicies = parsePolicies(policies);
432
const result = renderGP(mockProduct, parsedPolicies, []);
433
434
// Load the expected fixture file
435
const fixturePath = path.join(import.meta.dirname, 'fixtures', 'policies', 'win32', 'CodeOSS.admx');
436
const expectedContent = await fs.readFile(fixturePath, 'utf-8');
437
438
// Compare the rendered ADMX with the fixture
439
assert.strictEqual(result.admx, expectedContent, 'Windows ADMX should match the fixture');
440
});
441
442
test('should render Windows ADML from policies list', async () => {
443
const parsedPolicies = parsePolicies(policies);
444
const result = renderGP(mockProduct, parsedPolicies, []);
445
446
// Load the expected fixture file
447
const fixturePath = path.join(import.meta.dirname, 'fixtures', 'policies', 'win32', 'en-us', 'CodeOSS.adml');
448
const expectedContent = await fs.readFile(fixturePath, 'utf-8');
449
450
// Find the en-us ADML
451
const enUsAdml = result.adml.find(a => a.languageId === 'en-us');
452
assert.ok(enUsAdml, 'en-us ADML should exist');
453
454
// Compare the rendered ADML with the fixture
455
assert.strictEqual(enUsAdml.contents, expectedContent, 'Windows ADML should match the fixture');
456
});
457
458
test('should render macOS manifest with fr-fr locale', async () => {
459
const parsedPolicies = parsePolicies(policies);
460
const result = renderMacOSPolicy(mockProduct, parsedPolicies, frenchTranslations);
461
462
// Load the expected fixture file
463
const fixturePath = path.join(import.meta.dirname, 'fixtures', 'policies', 'darwin', 'fr-fr', 'com.visualstudio.code.oss.plist');
464
const expectedContent = await fs.readFile(fixturePath, 'utf-8');
465
466
// Find the fr-fr manifest
467
const frFrManifest = result.manifests.find(m => m.languageId === 'fr-fr');
468
assert.ok(frFrManifest, 'fr-fr manifest should exist');
469
470
// Compare the rendered manifest with the fixture, ignoring the timestamp
471
const normalizeTimestamp = (content: string) => content.replace(/<date>.*?<\/date>/, '<date>TIMESTAMP</date>');
472
assert.strictEqual(
473
normalizeTimestamp(frFrManifest.contents),
474
normalizeTimestamp(expectedContent),
475
'macOS fr-fr manifest should match the fixture (ignoring timestamp)'
476
);
477
});
478
479
test('should render Windows ADML with fr-fr locale', async () => {
480
const parsedPolicies = parsePolicies(policies);
481
const result = renderGP(mockProduct, parsedPolicies, frenchTranslations);
482
483
// Load the expected fixture file
484
const fixturePath = path.join(import.meta.dirname, 'fixtures', 'policies', 'win32', 'fr-fr', 'CodeOSS.adml');
485
const expectedContent = await fs.readFile(fixturePath, 'utf-8');
486
487
// Find the fr-fr ADML
488
const frFrAdml = result.adml.find(a => a.languageId === 'fr-fr');
489
assert.ok(frFrAdml, 'fr-fr ADML should exist');
490
491
// Compare the rendered ADML with the fixture
492
assert.strictEqual(frFrAdml.contents, expectedContent, 'Windows fr-fr ADML should match the fixture');
493
});
494
495
test('should render Linux policy JSON from policies list', async () => {
496
const parsedPolicies = parsePolicies(policies);
497
const result = renderJsonPolicies(parsedPolicies);
498
499
// Load the expected fixture file
500
const fixturePath = path.join(import.meta.dirname, 'fixtures', 'policies', 'linux', 'policy.json');
501
const expectedContent = await fs.readFile(fixturePath, 'utf-8');
502
const expectedJson = JSON.parse(expectedContent);
503
504
// Compare the rendered JSON with the fixture
505
assert.deepStrictEqual(result, expectedJson, 'Linux policy JSON should match the fixture');
506
});
507
508
});
509
510